﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using JetBrains.Annotations;
using MugenMvvmToolkit.Annotations;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.Models;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;
#if TASKSUPPORT
using System.Threading;
using System.Threading.Tasks;
#endif

namespace MugenMvvmToolkit.ViewModels
{
    /// <summary>
    ///     Represents the base class for all view models.
    /// </summary>
#if WPF
    [Serializable]
#endif
    [DataContract(IsReference = true)]
    [BaseViewModel(Priority = 9)]
    public abstract class ViewModelBase : NotifyPropertyChangedBase, IDesignViewModel, IHandler<BeginBusyMessage>, IHandler<EndBusyMessage>, IHandler<IStateChangedMessage>
    {
        #region Fields

        private IViewManager _viewManager;
        private readonly List<Action<IViewModel>> _invokeAfterInitActions;
        private readonly Dictionary<Guid, object> _busyCollection;
        private readonly object _locker;
        private object _busyMessage;
        private bool _isBusy;
        private bool _isDisposed;
        private bool _isDisposing;
        private bool _isInitialized;
        private bool _isFullyInitialized;
        private IViewModelSettings _settings;
        private IIocAdapter _iocAdapter;
        private readonly WeakListenersCollection _observers;
        private IViewModelProvider _viewModelProvider;
#if TASKSUPPORT
        private readonly CancellationTokenSource _disposeCancellationToken;
#endif

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="ViewModelBase" /> class.
        /// </summary>
        protected ViewModelBase()
        {
            _invokeAfterInitActions = new List<Action<IViewModel>>();
            _busyCollection = new Dictionary<Guid, object>();
            _locker = new object();
            _observers = new WeakListenersCollection();
#if TASKSUPPORT
            _disposeCancellationToken = new CancellationTokenSource();
#endif
            Settings = GlobalSettings.DefaultViewModelSettings != null
                ? GlobalSettings.DefaultViewModelSettings.Clone()
                : new DefaultViewModelSettings();
            ViewModelType = GetType();
            ViewModelExtensions.OnAuditInfoUpdated(AuditAction.Created, this);
            if (!GlobalSettings.IsDesignMode) return;
            //To force activation.
            var designBootstrapper = MvvmUtils.DesignBootstrapper;
            // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
            designBootstrapper.ToString();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the type of the current view model.
        /// </summary>
        internal Type ViewModelType { get; private set; }

        /// <summary>
        ///     Gets the collection of observers
        /// </summary>
        [NotNull]
        protected internal ICollection<object> Observers
        {
            get { return _observers; }
        }

        /// <summary>
        /// Gets or sets the default view model provider to create view models.
        /// </summary>
        [ShouldBeInitialized]
        protected IViewModelProvider ViewModelProvider
        {
            get { return _viewModelProvider; }
            set
            {
                Should.PropertyBeNotNull(value, "ViewModelProvider");
                _viewModelProvider = value;
            }
        }

        #endregion

        #region Implementation of IViewModel

#if TASKSUPPORT
        /// <summary>
        /// Gets the cancellation token that uses to cancel an operation when the current view model will be disposed.
        /// </summary>
        public CancellationToken DisposeCancellationToken
        {
            get { return _disposeCancellationToken.Token; }
        }
#endif
        /// <summary>
        ///     Gets the initialized state of the current view model.
        /// </summary>
        public bool IsInitialized
        {
            get
            {
                using (DeadLockDetector.Lock(_locker))
                    return _isInitialized;
            }
        }

        /// <summary>
        ///     Gets the busy state of the current view model.
        /// </summary>
        public virtual bool IsBusy
        {
            get { return _isBusy; }
            private set
            {
                if (_isBusy == value) return;
                _isBusy = value;
                OnPropertyChanged("IsBusy");
            }
        }

        /// <summary>
        ///     Gets the information message for busy dialog.
        /// </summary>
        public virtual object BusyMessage
        {
            get { return _busyMessage; }
            private set
            {
                if (_busyMessage == value) return;
                _busyMessage = value;
                OnPropertyChanged("BusyMessage");
            }
        }

        /// <summary>
        ///     Gets the <see cref="IViewModelSettings" />.
        /// </summary>
        public IViewModelSettings Settings
        {
            get { return _settings; }
            protected set
            {
                Should.PropertyBeNotNull(value, "Settings");
                _settings = value;
                OnPropertyChanged("Settings");
            }
        }

        /// <summary>
        ///     Gets a value indicating whether this instance is disposed.
        /// </summary>
        public bool IsDisposed
        {
            get
            {
                using (DeadLockDetector.Lock(_locker))
                    return _isDisposed;
            }
        }

        /// <summary>
        ///     Gets the ioc adapter for current view model.
        /// </summary>
        [ShouldBeInitialized]
        public IIocAdapter IocAdapter
        {
            get
            {
                if (_iocAdapter == null)
                    Thrower.ViewModelIsNotInitialized(ViewModelType);
                return _iocAdapter;
            }
            private set
            {
                Should.PropertyBeNotNull(value, "IocAdapter");
                _iocAdapter = value;
            }
        }

        /// <summary>
        ///     Initializes the current view model with specified <see cref="IInitializationContext" />.
        /// </summary>
        /// <param name="context">
        ///     The specified <see cref="IInitializationContext" />.
        /// </param>
        void IViewModel.InitializeViewModel(IInitializationContext context)
        {
            Should.NotBeNull(context, "context");
            // ReSharper disable NotResolvedInText
            Should.NotBeNull(context.IocAdapter, "context.IocAdapter");
            Should.NotBeNull(context.ActivationPolicy, "context.ActivationPolicy");
            // ReSharper restore NotResolvedInText
            using (DeadLockDetector.Lock(_locker))
            {
                if (_isInitialized)
                    Thrower.ViewModelWasInitialized();
                _isInitialized = true;
            }
            context.ViewModel = this;
            if (!Equals(_iocAdapter, context.IocAdapter))
                IocAdapter = context.IocAdapter;
            if (IsDefaultThreadManager)
                ThreadManager = IocAdapter.Get<IThreadManager>();
            if (ViewModelProvider == null)
                ViewModelProvider = IocAdapter.Get<IViewModelProvider>();

            InitializeDisplayName();
            InitializeView(context);
            InitializeParentViewModel(context);
            OnInitializing(context);
            OnInitializedInternal();
            OnInitialized();
            using (DeadLockDetector.Lock(_locker))
            {
                _isFullyInitialized = true;
            }
            _invokeAfterInitActions.ForEach(afterInitAction => afterInitAction(this));
            _invokeAfterInitActions.Clear();
            OnPropertyChanged("IsInitialized");
            ViewModelExtensions.OnAuditInfoUpdated(AuditAction.Initialized, this);
        }

        /// <summary>
        ///     Begins to indicate that the current view model is busy.
        /// </summary>
        /// <param name="message">
        ///    The specified message for the <see cref="IViewModel.BusyMessage" /> property.
        /// </param>
        /// <returns>Id of the operation.</returns>
        public Guid BeginBusy(object message = null)
        {
            EnsureIsNotDisposed();
            Guid newGuid = Guid.NewGuid();
            if (message == null)
                message = GetDefaultBusyMessage();
            AddBusy(newGuid, message, true);
            return newGuid;
        }

        /// <summary>
        ///     Ends to indicate that the current view model is busy.
        /// </summary>
        /// <param name="idBusy">Id of the operation to end.</param>
        public void EndBusy(Guid idBusy)
        {
            RemoveBusy(idBusy, true);
        }

        /// <summary>
        ///     Clears all busy operations.
        /// </summary>
        public void ClearBusy()
        {
            EnsureIsNotDisposed();
            Guid[] guids;
            using (DeadLockDetector.Lock(_busyCollection))
                guids = _busyCollection.Select(c => c.Key).ToArray();
            foreach (Guid guid in guids)
                EndBusy(guid);
        }

        /// <summary>
        ///Invokes an action after the current view model was initialized, if the view model is initialized action will invoked immediately.
        /// </summary>
        /// <param name="action">The specified delegate to invoke.</param>
        void IViewModel.InvokeAfterInitialization(Action<IViewModel> action)
        {
            using (DeadLockDetector.Lock(_locker))
            {
                if (!_isFullyInitialized)
                {
                    _invokeAfterInitActions.Add(action);
                    return;
                }
            }
            action(this);
        }

        /// <summary>
        ///     Occurred after disposed current <see cref="IViewModel" />.
        /// </summary>
#if WPF
        [field: NonSerialized]
#endif
        public event Action<IViewModel> Disposed;

        /// <summary>
        ///     Initializes the current view model in design mode.
        /// </summary>
        void IDesignViewModel.InitializeViewModel()
        {
            MvvmUtils
                .DesignBootstrapper
                .IocAdapter
                .Get<IViewModelProvider>()
                .InitializeViewModel(this, ActivationPolicy.WithoutViewUseParentIocAdaper, null);
        }

        /// <summary>
        ///     Subscribes an instance to events.
        /// </summary>
        /// <param name="instance">The instance to subscribe for event publication.</param>
        public virtual bool Subscribe(object instance)
        {
            if (instance == this || !_observers.Add(instance))
                return false;
            var vm = instance as IViewModel;
            if (vm != null)
                NotifyBeginBusy(vm);
            return true;
        }

        /// <summary>
        ///     Unsubscribes the instance from all events.
        /// </summary>
        /// <param name="instance">The instance to unsubscribe.</param>
        public virtual bool Unsubscribe(object instance)
        {
            if (!_observers.Remove(instance))
                return false;
            var vm = instance as IViewModel;
            if (vm != null)
                NotifyEndBusy(vm);
            return true;
        }

        #endregion

        #region Implementation of IHandler

        /// <summary>
        ///     Handles the message.
        /// </summary>
        /// <param name="sender">The object that raised the event.</param>
        /// <param name="message">Information about event.</param>
        void IHandler<BeginBusyMessage>.Handle(object sender, BeginBusyMessage message)
        {
            var messageMode = Settings.HandleBusyMessageMode;
            if (messageMode.HasFlag(HandleType.Handle))
                AddBusy(message.Id, message.Message, false);
            if (messageMode.HasFlag(HandleType.NotifyListeners))
                Publish(sender, message);
        }

        /// <summary>
        ///     Handles the message.
        /// </summary>
        /// <param name="sender">The object that raised the event.</param>
        /// <param name="message">Information about event.</param>
        void IHandler<EndBusyMessage>.Handle(object sender, EndBusyMessage message)
        {
            RemoveBusy(message.Id, false);
            Publish(sender, message);
        }

        /// <summary>
        ///     Handles the message.
        /// </summary>
        /// <param name="sender">The object that raised the event.</param>
        /// <param name="message">Information about event.</param>
        void IHandler<IStateChangedMessage>.Handle(object sender, IStateChangedMessage message)
        {
            Publish(sender, message);
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        ///     Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            InvokeAfterInitialization(() =>
            {
                using (DeadLockDetector.Lock(_locker))
                {
                    if (_isDisposing || _isDisposed) return;
                    _isDisposing = true;
                }
                GC.SuppressFinalize(this);
                OnDisposeInternal(true);
                OnDispose(true);
                OnDisposed();
                DisposeInternal();
                _isDisposed = true;
            });
        }

        #endregion

        #region Overrides of NotifyPropertyChangedBase

        /// <summary>
        ///     Calls the event for the specified property.
        /// </summary>
        /// <param name="args">The specified property args.</param>
        /// <param name="executionMode">
        ///     Specifies the execution mode for raise property changed event.
        /// </param>
        protected override void OnPropertyChanged(PropertyChangedEventArgs args, ExecutionType executionMode)
        {
            if (IsNotificationsSuspended)
            {
                IsNotificationsDirty = true;
                return;
            }
            executionMode.Invoke(ThreadManager, () =>
            {
                base.OnPropertyChanged(args, ExecutionType.None);
                Publish(args);
                Publish(StateChangedMessage.Empty);
            });
        }

        #endregion

        #region Work with IoC

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <typeparam name="T">The type of view model.</typeparam>
        /// <param name="getViewModelFunc">The specified delegate to create view model.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [ShouldBeInitialized]
        protected internal T GetViewModel<T>(GetViewModelDelegate<T> getViewModelFunc, IActivationPolicy policy = null, ListenType? listenMode = null)
            where T : IViewModel
        {
            EnsureIsNotDisposed();
            return ViewModelProvider.GetViewModel(getViewModelFunc, policy, this, listenMode);
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <param name="getViewModel">The specified delegate to create view model.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [ShouldBeInitialized]
        protected internal IViewModel GetViewModel(GetViewModelDelegate<IViewModel> getViewModel, IActivationPolicy policy = null, ListenType? listenMode = null)
        {
            EnsureIsNotDisposed();
            return ViewModelProvider.GetViewModel(getViewModel, policy, this, listenMode);
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <typeparam name="T">The type of view model.</typeparam>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>        
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [ShouldBeInitialized]
        protected internal T GetViewModel<T>(IActivationPolicy policy = null, ListenType? listenMode = null)
            where T : IViewModel
        {
            EnsureIsNotDisposed();
            return ViewModelProvider.GetViewModel<T>(policy, this, listenMode);
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <param name="viewModelType">The type of view model.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>        
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [ShouldBeInitialized]
        protected internal IViewModel GetViewModel([NotNull, ViewModelTypeRequired]Type viewModelType, IActivationPolicy policy = null, ListenType? listenMode = null)
        {
            EnsureIsNotDisposed();
            return ViewModelProvider.GetViewModel(viewModelType, policy, this, listenMode);
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <typeparam name="T">The type of view model.</typeparam>
        /// <param name="getViewModelFunc">The specified delegate to create view model.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <param name="withoutView">Parameter responsible to initialize the view.</param>        
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [ShouldBeInitialized]
        protected internal T GetViewModel<T>(GetViewModelDelegate<T> getViewModelFunc, ListenType listenMode, bool withoutView = false)
            where T : IViewModel
        {
            return GetViewModel(getViewModelFunc, GetActivationPolicy(listenMode, withoutView));
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <param name="getViewModel">The specified delegate to create view model.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>
        /// <param name="withoutView">Parameter responsible to initialize the view.</param>
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [ShouldBeInitialized]
        protected internal IViewModel GetViewModel(GetViewModelDelegate<IViewModel> getViewModel, ListenType listenMode, bool withoutView = false)
        {
            return GetViewModel(getViewModel, GetActivationPolicy(listenMode, withoutView));
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <typeparam name="T">The type of view model.</typeparam>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <param name="withoutView">Parameter responsible to initialize the view.</param>
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [ShouldBeInitialized]
        protected internal T GetViewModel<T>(ListenType listenMode, bool withoutView = false)
            where T : IViewModel
        {
            return (T)GetViewModel(typeof(T), GetActivationPolicy(listenMode, withoutView));
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <param name="viewModelType">The type of view model.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <param name="withoutView">Parameter responsible to initialize the view.</param>
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [ShouldBeInitialized]
        protected internal IViewModel GetViewModel([NotNull, ViewModelTypeRequired]Type viewModelType, ListenType listenMode, bool withoutView = false)
        {
            return GetViewModel(viewModelType, GetActivationPolicy(listenMode, withoutView));
        }

        /// <summary>
        ///     Gets an instance of the specified service.
        /// </summary>
        /// <param name="service">The specified service type.</param>
        /// <param name="name">The specified binding name.</param>
        /// <param name="iocParameters">The specified parameters.</param>
        /// <returns>An instance of the service.</returns>
        [ShouldBeInitialized]
        protected internal object Get([NotNull]Type service, string name = null, params IIocParameter[] iocParameters)
        {
            EnsureIsNotDisposed();
            Should.NotBeNull(service, "service");
            IocAdapter.NotBeDisposed();
            if (typeof(IViewModel).IsAssignableFrom(service))
                Thrower.CreateViewModelInvalidMethod();
            return IocAdapter.Get(service, name, iocParameters);
        }

        /// <summary>
        ///     Gets an instance of the specified service.
        /// </summary>
        /// <typeparam name="T">The specified service type.</typeparam>
        /// <param name="iocParameters">The specified parameters.</param>
        /// <returns>An instance of the service.</returns>
        [ShouldBeInitialized]
        protected internal T Get<T>(params IIocParameter[] iocParameters)
        {
            EnsureIsNotDisposed();
            return (T)Get(typeof(T), null, iocParameters);
        }

        /// <summary>
        ///     Gets an instance of the specified service.
        /// </summary>
        /// <typeparam name="T">The specified service type.</typeparam>
        /// <param name="name">The specified binding name.</param>
        /// <param name="iocParameters">The specified parameters.</param>
        /// <returns>An instance of the service.</returns>
        [ShouldBeInitialized]
        protected internal T Get<T>(string name, params IIocParameter[] iocParameters)
        {
            EnsureIsNotDisposed();
            return (T)Get(typeof(T), name, iocParameters);
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Publishes a message.
        /// </summary>
        /// <param name="message">The message instance.</param>
        protected void Publish([NotNull]object message)
        {
            Publish(this, message);
        }

        /// <summary>
        /// Invokes an action in new thread and wrap it to busy indicator.
        /// </summary>
        /// <param name="action">The specified <see cref="Action"/>.</param>
        /// <param name="message">The specified message for the <see cref="BusyMessage" /> property.</param>
        protected internal void InvokeWithBusyIndicator(Action action, object message = null)
        {
            EnsureIsNotDisposed();
            Should.NotBeNull(action, "action");
            if (message == null)
                message = GetDefaultBusyMessage();
            Guid beginBusy = BeginBusy(message);
            ThreadManager.InvokeAsync(o =>
                                          {
                                              try
                                              {
                                                  action();
                                              }
                                              finally
                                              {
                                                  EndBusy(beginBusy);
                                              }
                                          });
        }

        /// <summary>
        /// Invokes an action after the current view model was initialized, if the view model is initialized action will invoked immediately.
        /// </summary>
        /// <param name="action">The specified delegate to invoke.</param>
        protected void InvokeAfterInitialization(Action action)
        {
            ((IViewModel)this).InvokeAfterInitialization(model => action());
        }

        internal object GetDefaultBusyMessage()
        {
            if (_isInitialized)
                return Settings.DefaultBusyMessage;
            return GlobalSettings.DefaultViewModelSettings != null
                ? GlobalSettings.DefaultViewModelSettings.DefaultBusyMessage
                : string.Empty;
        }

        private static IActivationPolicy GetActivationPolicy(ListenType listenMode, bool withoutView = false)
        {
            return new ActivationPolicy(GlobalSettings.DefaultActivationPolicy)
            {
                ListenMode = listenMode,
                WithoutViewValue = withoutView
            };
        }

        private void InitializeParentViewModel(IInitializationContext context)
        {
            IViewModel parentViewModel = context.ParentViewModel;
            if (parentViewModel == null)
                return;
            var listenMode = context
                .ActivationPolicy
                .ListenMode
                .GetValueOrDefault(GlobalSettings.ListenViewModelMode);
            if (listenMode.HasFlag(ListenType.Child))
                Subscribe(parentViewModel);
            if (listenMode.HasFlag(ListenType.Parent))
                parentViewModel.Subscribe(this);
        }

        private void InitializeView(IInitializationContext context)
        {
            if (context.ActivationPolicy.WithoutView)
                return;
            var viewManager = context.IocAdapter.Get<IViewManager>();
            if (viewManager.InitializeView(context))
                _viewManager = viewManager;
        }

        private void InitializeDisplayName()
        {
            var hasDisplayName = this as IHasDisplayName;
            if (hasDisplayName != null && string.IsNullOrEmpty(hasDisplayName.DisplayName)
                && IocAdapter.CanResolve(typeof(IDisplayNameProvider)))
#if NETFX_CORE
                hasDisplayName.DisplayName = Get<IDisplayNameProvider>().GetDisplayName(ViewModelType.GetTypeInfo());
#else
                hasDisplayName.DisplayName = Get<IDisplayNameProvider>().GetDisplayName(ViewModelType);
#endif
        }

        private void NotifyBeginBusy(IViewModel viewModel)
        {
            using (DeadLockDetector.Lock(_busyCollection))
            {
                if (_busyCollection.Count == 0) return;
                foreach (var o in _busyCollection.ToList())
                {
                    var message = new BeginBusyMessage(o.Key, o.Value);
                    WeakListenersCollection.PublishToItem(viewModel, this, message);
                }
            }
        }

        private void NotifyEndBusy(IViewModel viewModel)
        {
            using (DeadLockDetector.Lock(_busyCollection))
            {
                if (_busyCollection.Count == 0) return;
                foreach (var o in _busyCollection.Keys.ToList())
                {
                    var message = new EndBusyMessage(o);
                    WeakListenersCollection.PublishToItem(viewModel, this, message);
                }
            }
        }

        /// <summary>
        ///     Adds busy operation.
        /// </summary>
        private void AddBusy(Guid idBusy, object message, bool notify)
        {
            using (DeadLockDetector.Lock(_busyCollection))
            {
                if (_busyCollection.ContainsKey(idBusy)) return;
                _busyCollection.Add(idBusy, message);
                BusyMessage = message;
                IsBusy = true;
            }
            OnBeginBusy(idBusy, message);
            if (notify)
                Publish(new BeginBusyMessage(idBusy, message));
        }

        /// <summary>
        ///     Removes busy operation.
        /// </summary>
        private void RemoveBusy(Guid idBusy, bool notify)
        {
            using (DeadLockDetector.Lock(_busyCollection))
            {
                if (!_busyCollection.Remove(idBusy)) return;
                if (_busyCollection.Count == 0)
                {
                    IsBusy = false;
                    BusyMessage = null;
                }
                else
                    BusyMessage = _busyCollection.Values.LastOrDefault();
            }
            OnEndBusy(idBusy);
            if (notify)
                Publish(new EndBusyMessage(idBusy));
        }

        /// <summary>
        ///     Clears all event from view model command and PropertyChanged event.
        /// </summary>
        private void DisposeInternal()
        {
            //Disposing commands, if any.
            if (Settings.DisposeCommands)
                MvvmUtilsInternal.DisposeCommands(this);

            ClearBusy();
            ClearPropertyChangedEvents();
            foreach (var source in _observers.ToList())
            {
                Unsubscribe(source);
            }

            //Clearing view, if any.
            if (_viewManager != null)
            {
                _viewManager.CleanupView(this);
                _viewManager = null;
            }

            //Disposing ioc adapter.
            if (Settings.DisposeIocAdapter)
                IocAdapter.Dispose();

#if TASKSUPPORT
            _disposeCancellationToken.Cancel();
#endif
            ViewModelExtensions.OnAuditInfoUpdated(AuditAction.Disposed, this);
            //Clear disposed events, if any.
            var disposed = Disposed;
            if (disposed == null) return;
            foreach (Action<IViewModel> @delegate in disposed.GetInvocationList())
                Disposed -= @delegate;
        }

        private void OnDisposed()
        {
            Action<IViewModel> handler = Disposed;
            if (handler != null)
                handler(this);
        }

        /// <summary>
        /// Makes sure that the object is not disposed.
        /// </summary>
        protected internal void EnsureIsNotDisposed()
        {
            this.NotBeDisposed(_isDisposed);
        }

        #endregion

        #region Virtual methods

        /// <summary>
        ///     Publishes a message.
        /// </summary>
        /// <param name="sender">The specified sender.</param>
        /// <param name="message">The message instance.</param>
        protected virtual void Publish(object sender, object message)
        {
            Should.NotBeNull(message, "message");
            _observers.Publish(this, message);
        }

        /// <summary>
        ///  Occurs after busy operation is occurred.
        /// </summary>
        /// <param name="id">The specified id.</param>
        /// <param name="message">The specified message.</param>
        protected virtual void OnBeginBusy(Guid id, object message)
        {
        }

        /// <summary>
        ///  Occurs after busy operation is ended.
        /// </summary>
        /// <param name="id">The specified id.</param>
        protected virtual void OnEndBusy(Guid id)
        {

        }

        /// <summary>
        ///     Occurs after the view model is fully loaded.
        /// </summary>
        internal virtual void OnInitializedInternal()
        {
        }

        /// <summary>
        ///     Occurs during the initialization of the view model.
        /// </summary>
        protected virtual void OnInitializing(IInitializationContext context)
        {
        }

        /// <summary>
        ///     Occurs after the view model is fully loaded.
        /// </summary>
        protected virtual void OnInitialized()
        {
        }

        /// <summary>
        ///     Occurs after the current view model is disposed, use for clear resource and event listeners(Internal only).
        /// </summary>
        internal virtual void OnDisposeInternal(bool disposing)
        {
        }

        /// <summary>
        ///     Occurs after the current view model is disposed, use for clear resource and event listeners.
        /// </summary>
        protected virtual void OnDispose(bool disposing)
        {
        }

        #endregion

        #region Destructor

        /// <summary>
        /// Destructor of view model.
        /// </summary>
        ~ViewModelBase()
        {
            OnDisposeInternal(false);
            OnDispose(false);
            if (!GlobalSettings.IsDebugMode) return;
            ViewModelExtensions.OnAuditInfoUpdated(AuditAction.Finalized, this);
#if !SILVERLIGHT
#if !NETFX_CORE
            Trace.WriteLine(string.Format("{0} ({1}) Finalized", ViewModelType, GetHashCode()));
#endif
#endif
        }

        #endregion
    }
}